package com.paraz.cst;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 
 * Main Looper bean for handling.
 * Version with no sync.
 * 
 * @author mparaz
 *
 */
public class AsyncCstLooper implements CstProcessorResultHandler<Integer>, CstLooper<Integer> {
	public static final String LINE_TERMINATOR = "\r\n";

	/**
	 * CstProcessor to use.
	 */
	private CstProcessor<Integer> cstProcessor;

	
	/**
	 * Counter for pick-up.
	 */
	private final AtomicInteger counter = new AtomicInteger();

	/**
	 * Map for pick-up.
	 * 
	 * TODO Non-functional requirement: async requests must clear map.
	 */
	private final ConcurrentMap<Integer, CstProcessorResult> resultMap = new ConcurrentHashMap<Integer, CstProcessorResult>();
	
	/**
	 * Running.
	 * 
	 * Volatile will make the change work across different threads.
	 * 
	 */
	private volatile boolean running = true;
	
	public CstProcessor<Integer> getCstProcessor() {
		return cstProcessor;
	}

	public void setCstProcessor(CstProcessor<Integer> cstProcessor) {
		this.cstProcessor = cstProcessor;
	}

	
	
	/**
	 * Loop the main logic.
	 * 
	 */
	public void loop(CstSocket cstSocket) {
		loop(false, cstSocket);
	}
	
	/**
	 * Loop the main logic - actual
	 * 
	 */
	void loop(boolean oneTime, CstSocket cstSocket) {
		final CstInput cstInput = cstSocket.getCstInput();
		final CstOutput cstOutput = cstSocket.getCstOutput();
		while (running) {
			try {
				// Convert to String
				final String stringRead = cstInput.read();

				// Handle the pick-up request.
				if (stringRead.startsWith("P")) {
					pickup(cstOutput, stringRead);
					continue;
				}
				
				// Convert to a request
				final CstProcessorRequest request = CstProcessorRequest.fromString(stringRead);
				
				// Increment the counter.
				final int intCounter = counter.getAndIncrement();
				
				// Feed to the processor.
				CstProcessorResult result = getCstProcessor().process(request, intCounter, this);
				
				if (result == CstProcessorResult.NONE) {
					// Return right away, the client should pick it up later.
					final String str = "P " + intCounter + LINE_TERMINATOR;
					cstOutput.write(str);
				} else {
					// Return on the same socket.
					final String str = result.getResult() + LINE_TERMINATOR;
					cstOutput.write(str);
				}
			} catch (CstException e) {
				// Stop running
				break;
			}
			if (oneTime) {
				break;
			}
		}
	}
	
	/**
	 * Handle the pick-up request.
	 * 
	 * @param cstOutput
	 * @param stringRead
	 */
	private void pickup(CstOutput cstOutput, String stringRead) {
		// Get the 2nd parameter as an integer.
		final String[] sa = stringRead.split("\\s");
		
		// TODO No error checking here.
		final Integer key = Integer.parseInt(sa[1]);
		
		final CstProcessorResult result = resultMap.get(key);
		try {
			final String s = result.getResult() + LINE_TERMINATOR;
			cstOutput.write(s);
		} catch (CstException e) {
			// Ignored
		}
	}
	
	
	public void stop() {
		running = false;
	}

	/**
	 * Result will be delivered on a separate thread.
	 */
	public void result(Integer reference, CstProcessorResult result) {
		if (reference instanceof Integer) {
			// Ready for pick-up.
			resultMap.put(reference, result);
		} else {
			// This is abnormal but generics will NOT guarantee this won't happen.
			throw new IllegalArgumentException("reference must be an Integer");
		}
	}
}
